#include <kutils.h>
#include "memory.h"
#include "elf.h"
#include "vfs.h"
#include "display.h"
#include "paging.h"

s32int elf_is_elf(elf_eheader_t* header);
s32int elf_load_pheader(u8int* header_start, u8int* file_start);
u32int elf_load_eheader(u8int* header_start);

extern address_space_t* active_space;

s32int elf_is_elf(elf_eheader_t* header)
{
	u8int valid_sig[16] = {0x7F,'E','L','F',1,1,1,0,0,0,0,0,0,0,0,0};
	s32int i = 0;

	for(i = 0; i < 16; i++)
	{
		if(header->e_ident[i] != valid_sig[i])
			return 0;
	}
	return 1;
}

s32int elf_load_pheader(u8int* header_start, u8int* file_start)
{
	elf_pheader_t* pheader = (elf_pheader_t*)header_start;
#ifdef FLUIDIUM_DEBUG
	write_string("ELF: type: ");write_hex(pheader->p_type);
#endif
	if(pheader->p_type != 0x1)
	{
#ifdef FLUIDIUM_DEBUG
		write_string(" Not loadable. Failing.\n");
#endif
		return 0;
	}
#ifdef FLUIDIUM_DEBUG
	write_string(" Loadable.\n");
	write_string("ELF: flags: ");write_hex(pheader->p_flags);
	if(pheader->p_flags & 0x1){ write_string(" exec");  }
	if(pheader->p_flags & 0x2){ write_string(" write"); }
	if(pheader->p_flags & 0x4){ write_string(" read");  }
	write_string("\n");
	write_string("ELF: Reading ");write_number(pheader->p_file_size);write_string(" bytes from offset ");
	write_number(pheader->p_offset);write_string(" in the file...\nELF: Writing ");write_number(pheader->p_mem_size);
	write_string(" bytes to ");write_hex(pheader->p_virtual_addr);write_string(" in memory...\n");
#endif
	//actually do the loading
	page_alloc(active_space, pheader->p_virtual_addr, pheader->p_virtual_addr + pheader->p_mem_size);
	u32int copy_size = 0;
	if(pheader->p_mem_size < pheader->p_file_size)
		copy_size = pheader->p_mem_size;
	else
		copy_size = pheader->p_file_size;

	memset((u32int*)pheader->p_virtual_addr, 0, (size_t)pheader->p_mem_size);
	memcpy((u32int*)pheader->p_virtual_addr, (u32int*)(file_start + pheader->p_offset), (size_t)copy_size);
#ifdef FLUIDIUM_DEBUG
	write_string("ELF: Segment loaded.\n");
#endif
	return 1;
}

u32int elf_load_eheader(u8int* header_start)
{
	elf_eheader_t* eheader = (elf_eheader_t*)header_start;
	u32int i = 0;

	if(elf_is_elf(eheader))
	{
#ifdef FLUIDIUM_DEBUG
		write_string("ELF: file is an ELF file.\n");
#endif
		for(i = 0; i < eheader->e_phnum; i++)
		{
#ifdef FLUIDIUM_DEBUG
			write_string("ELF: pheader ");
			write_number(i);write_string(":\n");
#endif
			if(!elf_load_pheader((u8int*)(header_start + eheader->e_phoff + (eheader->e_phentsize * i)), header_start))
			{
				return 0;
			}
		}
		return eheader->e_entry;
	}
	else
	{
#ifdef FLUIDIUM_DEBUG
		write_string("ELF: file is not an ELF file.\n");
#endif
		halt();
		return 0;
	}		
	return 0;
}

u32int elf_load(s8int* path)
{
	if(!path)
		return 0;

#ifdef FLUIDIUM_DEBUG
		write_string("ELF: Loading '");
		write_string(path);
		write_string("'...\n");
#endif	

	u32int entry = 0;
	fs_node_t* file = open_fs(path);
	if(file)
	{
#ifdef FLUIDIUM_DEBUG
		write_string("ELF: Opened\n");
#endif
		u8int *file_buffer = (u8int*)kmalloc(file->length);
		seek_fs(file, 0);
		if(read_fs(file, file->length, file_buffer))
		{
#ifdef FLUIDIUM_DEBUG
			write_string("ELF: Read\n");
#endif
			entry = elf_load_eheader(file_buffer);
			if(!entry)
			{
				write_string("ELF: failed parsing and/or loading ELF file.\n");
			}
		}
		else
		{
			write_string("ELF: file could not be read.\n");
		}
		//any failure in the above code falls through to here 
		//where we go ahed and attempt to close the file
		if(!close_fs(file))
		{
			write_string("ELF: file could not be closed.\n");
		}
		kfree(file_buffer);
	}
	else
	{
		write_string("ELF: file could not be opened.\n");
		return 0;
	}
	return entry;
}
